﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows;
using System.IO.Packaging;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace MTS
{
    [Serializable()]

    class LevelChunk : IComparable<LevelChunk>, IEquatable<LevelChunk>
    {
        int mapX = 16;
        int mapY = 16;
        int mapZ = 128;
        private Region _region;
        private long _worldX;
        private long _worldY;
        private long _localX;
        private long _localY;
        private Boolean _hasBlocks = true;
        private RenderSettings settings;
        private Int32 lastWritten;
        [NonSerialized] BlockColors colorer;
        private bool cached = false;

        private long LocalX
        {
            get
            {
                return _localX;
            }
        }

        private long LocalY
        {
            get
            {
                return _localY;
            }
        }
        public bool Cached
        {
            get
            {
                return cached;
            }
        }

        public RenderSettings Settings
        {
            get
            {
                return this.settings;
            }
        }

        public int lastModified
        {
            get
            {
                return lastWritten;
            }
        }

        public BlockColors Colorer
        {
            get
            {
                if (colorer == null)
                {
                    colorer = new BlockColors();
                }
                return colorer;
            }
            set
            {
                colorer = value;
            }
        }

        public TileImage Image(ChunkCache cache)
        {

            if (cache.imageExists(this.WorldX, this.WorldY))
            {
                // it exists, open it
                try
                {
                    TileImage cacheImage = cache.getImage(this.WorldX, this.WorldY);
                    if (cacheImage.ReadTime >= this.lastWritten)
                    {
                        cached = true;
                        return cacheImage;
                    }
                    else
                    {
                        cached = false;
                    }
                    
                }
                catch (FileNotFoundException e)
                {
                    // chunk not found,
                    cached = false;
                }

            }
            
            
            cached = false;
            
            // make a new one or redraw the existing one
            TileImage image;
            if (settings.TileType.Equals(ImageType.CAVEMAP))
            {
                image = new TileImage(this.CavemapImage, this.WorldX, this.WorldY, this.terrainRect.Width, this.terrainRect.Height, this.Settings);
            }
            else if (settings.TileType.Equals(ImageType.HEIGHTMAP))
            {
                image = new TileImage(this.HeightmapImage, this.WorldX, this.WorldY, this.terrainRect.Width, this.terrainRect.Height, this.Settings);
            }
            else if (settings.TileType.Equals(ImageType.OBLIQUE))
            {
                image = new TileImage(this.ObliqueImage, this.WorldX, this.WorldY, this.obliqueRect.Width, this.obliqueRect.Height, this.Settings);
            }
            else if (settings.TileType.Equals(ImageType.SPECTROGRAPH))
            {
                image = new TileImage(SpectralImage, this.WorldX, this.WorldY, this.terrainRect.Width, this.terrainRect.Height, this.Settings);
            }
            else if (settings.TileType.Equals(ImageType.TERRAIN))
            {
                image = new TileImage(this.TerrainImage, this.WorldX, this.WorldY, this.terrainRect.Width, this.terrainRect.Height, this.Settings);
            }
            else
            {
                throw new Exception("Unsupported Tile Type");
            }
            image.ReadTime = this.lastWritten;
            image.ChunkHeight = 16;
            image.ChunkWidth = 16;


            cache.addImage(image);
            

            return image;
        }

        public byte[] SpectralImage
        {
            get
            {
                return renderSpectograph();
            }
        }

        public byte[] TerrainImage
        {
            get
            {
                return renderTerrain();
            }
        }

        public byte[] ObliqueImage
        {
            get
            {
                return renderOblique();
            }
        }

        public byte[] HeightmapImage
        {
            get
            {
                return renderHeightmap();
            }
        }

        public byte[] CavemapImage
        {
            get
            {
                return renderCavemap();
            }
        }

        public Int32Rect terrainRect
        {
            get
            {
                return new Int32Rect((int)WorldX, (int)WorldY, 16, 16);
            }
        }

        public Int32Rect obliqueRect
        {
            get
            {
                return new Int32Rect((int)WorldX, (int)WorldY, 16, 145);
            }
        }

        public Boolean hasBlocks
        {
            get
            {
                return _hasBlocks;
            }
        }
        public long WorldY
        {
            get
            {
                return _worldY;
            }
        }
        public long WorldX
        {
            get
            {
                return _worldX;
            }
        }

        public LevelChunk(Region r, RenderSettings settings, long worldX, long worldY, long localX, long localY, Int32 lastWritten)
        {
            this._region = r;
            this.settings = settings;
            this.lastWritten = lastWritten;
            char[] delimiters = {'.'};
            _worldX = worldX;
            _worldY = worldY;
            _localX = localX;
            _localY = localY;
        }

        private byte[] renderTerrain()
        {
            int _stride = this.mapX * settings.BytesPerPixel;
            byte[] outputBytes = new byte[this.mapX * this.mapY * settings.BytesPerPixel];
            byte[] blocks = this.loadBlocks();
            byte[] lighting = this.loadBlockLighting();
            byte[] heightmap = this.loadHeightmap();
            for (int z = 0; z < this.mapZ; z++)
            {
                for (int y = 0; y < this.mapY; y++)
                {
                    for (int x = 0; x < this.mapX; x++)
                    {
                        if (blocks == null) continue;

                        int offset = x * mapZ + y * mapZ * mapX + z;
                        int offsetAbove = x * mapZ + y * mapZ * mapX + Math.Min(z + 1, this.mapZ - 1);
                        int pixelOffset = ((this.mapX - x - 1) + y * mapX) * settings.BytesPerPixel;

                        Color tileColor, aboveColor;

                        tileColor = Colorer.getBlockColor(blocks[offset]);
                        aboveColor = Colorer.getBlockColor(blocks[offsetAbove]);

                        // Drawing code here
                        if (tileColor.A == 255 && aboveColor.A == 0)
                        {
                            // No point alpha blending if the new tile is fully solid
                            outputBytes[pixelOffset] = tileColor.B;
                            outputBytes[pixelOffset + 1] = tileColor.G;
                            outputBytes[pixelOffset + 2] = tileColor.R;
                            outputBytes[pixelOffset + 3] = tileColor.A;
                        }
                        else if (tileColor.A > 0 && aboveColor.A < 255)
                        {
                            Color existingColor = Color.FromArgb(outputBytes[pixelOffset + 3], outputBytes[pixelOffset + 2], outputBytes[pixelOffset + 1], outputBytes[pixelOffset]);
                            Color newColor = blendColors(tileColor, existingColor, z);
                            outputBytes[pixelOffset] = newColor.B;
                            outputBytes[pixelOffset + 1] = newColor.G;
                            outputBytes[pixelOffset + 2] = newColor.R;
                            outputBytes[pixelOffset + 3] = newColor.A;
                        }

                    }
                }
            }
            
            return outputBytes;
        }

        private byte[] renderOblique()
        {
            int _stride = this.mapX * settings.BytesPerPixel;
            byte[] outputBytes = new byte[this.mapX * (this.mapY + this.mapZ + 1) * settings.BytesPerPixel];
            byte[] blocks = this.loadBlocks();
            byte[] lighting = this.loadBlockLighting();
            byte[] heightmap = this.loadHeightmap();
            for (int z = 0; z < this.mapZ; z++)
            {
                for (int y = 0; y < this.mapY; y++)
                {
                    for (int x = 0; x < this.mapX; x++)
                    {
                        if (blocks == null) continue;

                        int offset = x * mapZ + y * mapZ * mapX + z;
                        int offsetAbove = x * mapZ + y * mapZ * mapX + Math.Min(z+1, this.mapZ-1);
                        int offsetInfront = x * mapZ + Math.Min(y+1, this.mapY-1) * mapZ * mapX + z;
                        int pixelOffset = ((this.mapX - x - 1) + ((this.mapZ - z - 1) + y) * mapX) * settings.BytesPerPixel;
                        int pixelOffsetAbove = ((this.mapX - x - 1) + ((this.mapZ - z - 1) + y + 1) * mapX) * settings.BytesPerPixel;
                        byte tile = blocks[offset];
                        
                        Color tileColor = Colorer.getBlockColor(tile);

                        if (tileColor.A.CompareTo(0) > 0)
                        {
                            //int skyLight = Math.Min(Math.Max(this._lightingLevel + (heightmap[x + y * mapX] - z), 0), this._lightingLevel);
                            int skyLightTop = this.settings.LightingLevel - Math.Min(Math.Max((heightmap[y + x * mapY] - z) / 8, 0), this.settings.LightingLevel);
                            int skyLightFront = this.settings.LightingLevel - Math.Min(Math.Max((heightmap[Math.Min(y + 1, this.mapY - 1) + x * mapY] - z) / 8, 0), this.settings.LightingLevel);
                            float topLightingMultiplier = (float)Math.Max(lighting[offsetAbove], (byte)skyLightTop) / (float)16;
                            float frontLightingMultiplier = (float)Math.Max(lighting[offsetInfront], (byte)skyLightFront) / (float)16;
                            outputBytes[pixelOffset] = (byte)(tileColor.B * topLightingMultiplier);
                            outputBytes[pixelOffset + 1] = (byte)(tileColor.G * topLightingMultiplier);
                            outputBytes[pixelOffset + 2] = (byte)(tileColor.R * topLightingMultiplier);
                            outputBytes[pixelOffset + 3] = 0xff;
                            outputBytes[pixelOffsetAbove] = (byte)Math.Max((tileColor.B - 20) * frontLightingMultiplier, 0);
                            outputBytes[pixelOffsetAbove + 1] = (byte)Math.Max((tileColor.G - 20) * frontLightingMultiplier, 0);
                            outputBytes[pixelOffsetAbove + 2] = (byte)Math.Max((tileColor.R - 20) * frontLightingMultiplier, 0);
                            outputBytes[pixelOffsetAbove + 3] = 0xff;
                        }
                    }
                }
            }
            return outputBytes;
        }

        private byte[] renderHeightmap()
        {
            byte[] outputBytes = new byte[this.mapX * this.mapY * settings.BytesPerPixel];
            byte[] heightmap = this.loadHeightmap();
            for (int y = 0; y < this.mapY; y++)
            {
                for (int x = 0; x < this.mapX; x++)
                {
                    if (heightmap == null) continue;

                    int offset = x + y * mapX;
                    int pixelOffset = ((this.mapY - y - 1) + x * mapY) * settings.BytesPerPixel;
                    byte height = (byte)Math.Abs((heightmap[offset] * 2) % 256);
                    Color blockColor = Color.FromArgb(0xff, height, height, height);
                    outputBytes[pixelOffset] = blockColor.B;
                    outputBytes[pixelOffset + 1] = blockColor.G;
                    outputBytes[pixelOffset + 2] = blockColor.R;
                    outputBytes[pixelOffset + 3] = blockColor.A;
                }
            }
            return outputBytes;
        }

        private byte[] renderCavemap()
        {
            byte[] outputBytes = new byte[this.mapX * this.mapY * settings.BytesPerPixel];
            byte[] heightmap = this.loadHeightmap();
            byte[] blocks = this.loadBlocks();
            for (int z = 0; z < this.mapZ; z++)
            {
                for (int y = 0; y < this.mapY; y++)
                {
                    for (int x = 0; x < this.mapX; x++)
                    {
                        if (heightmap == null || blocks == null) continue;

                        int offset = x * mapZ + y * mapZ * mapX + z;
                        int pixelOffset = ((this.mapX - x - 1) + y * mapX) * settings.BytesPerPixel;
                        int heightOffset = y + x * mapY;

                        if (z < heightmap[heightOffset] && blocks[offset] == 0x00)
                        {
                            byte height = (byte)((256 - (heightmap[y + x * mapY] - z) * 4) % 256);
                            Color blockColor = Color.FromArgb(0xff, height, height, height);
                            outputBytes[pixelOffset] = blockColor.B;
                            outputBytes[pixelOffset + 1] = blockColor.G;
                            outputBytes[pixelOffset + 2] = blockColor.R;
                            outputBytes[pixelOffset + 3] = blockColor.A;
                        }
                    }
                }
            }
            return outputBytes;
        }

        private byte[] renderSpectograph()
        {
            byte block = settings.SpectralBlock;
            byte[] outputBytes = new byte[this.mapX * this.mapY * settings.BytesPerPixel];
            byte[] blocks = this.loadBlocks();
            for (int x = 0; x < this.mapX; x++)
            {
                for (int y = 0; y < this.mapY; y++)
                {
                    if (blocks == null) continue;

                    byte count = 0;
                    int pixelOffset = ((this.mapX - x - 1) + y * mapX) * settings.BytesPerPixel;
                    for (int z = 0; z < this.mapZ; z++)
                    {
                        int offset = x * mapZ + y * mapZ * mapX + z;
                        if (blocks[offset] == block)
                        {
                            count++;
                        }
                    }
                    count = (byte)((count * 16) % 256);
                    Color blockColor = Color.FromArgb(0xff, count, count, count);
                    outputBytes[pixelOffset] = blockColor.B;
                    outputBytes[pixelOffset + 1] = blockColor.G;
                    outputBytes[pixelOffset + 2] = blockColor.R;
                    outputBytes[pixelOffset + 3] = blockColor.A;
                }
            }
            return outputBytes;
        }

        private byte[] loadBlocks()
        {
            if (this.hasBlocks)
            {
                byte[] blockTagTitle = new byte[9];
                blockTagTitle[0] = 0x07;
                blockTagTitle[1] = 0x00;
                blockTagTitle[2] = 0x06;
                blockTagTitle[3] = 0x42;
                blockTagTitle[4] = 0x6c;
                blockTagTitle[5] = 0x6f;
                blockTagTitle[6] = 0x63;
                blockTagTitle[7] = 0x6b;
                blockTagTitle[8] = 0x73;

                byte[] rawMap = this._region.ReadChunk(this.LocalX, this.LocalY);

                using (MemoryStream bytesIn = new MemoryStream(rawMap))
                {
                    BinaryReader inReader = new BinaryReader(bytesIn);
                    int position = -1;
                    for (int index = 0; index < rawMap.Length - blockTagTitle.Length; index++)
                    {
                        if (rawMap[index] == blockTagTitle[0]
                            && rawMap[index + 1] == blockTagTitle[1]
                            && rawMap[index + 2] == blockTagTitle[2]
                            && rawMap[index + 3] == blockTagTitle[3]
                            && rawMap[index + 4] == blockTagTitle[4]
                            && rawMap[index + 5] == blockTagTitle[5]
                            && rawMap[index + 6] == blockTagTitle[6]
                            && rawMap[index + 7] == blockTagTitle[7]
                            && rawMap[index + 8] == blockTagTitle[8]
                            )
                        {
                            position = index;
                            break;
                        }

                    }
                    if (position != -1 && position < rawMap.Length)
                    {
                        inReader.BaseStream.Seek(position + blockTagTitle.Length, SeekOrigin.Begin);
                        byte[] lengthBytes = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(lengthBytes);
                        }
                        int length = BitConverter.ToInt32(lengthBytes, 0);
                        byte[] blocks = inReader.ReadBytes(length);
                        return blocks;
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            else
            {
                return null;
            }
        }

        private byte[] loadHeightmap()
        {
            byte[] blockTagTitle = new byte[12];
            blockTagTitle[0] = 0x07;
            blockTagTitle[1] = 0x00;
            blockTagTitle[2] = 0x09;
            blockTagTitle[3] = 0x48;
            blockTagTitle[4] = 0x65;
            blockTagTitle[5] = 0x69;
            blockTagTitle[6] = 0x67;
            blockTagTitle[7] = 0x68;
            blockTagTitle[8] = 0x74;
            blockTagTitle[9] = 0x4d;
            blockTagTitle[10] = 0x61;
            blockTagTitle[11] = 0x70;
            if (this.hasBlocks)
            {
                byte[] rawMap = this._region.ReadChunk(this.LocalX, this.LocalY);
                using (MemoryStream bytesIn = new MemoryStream(rawMap))
                {
                    BinaryReader inReader = new BinaryReader(bytesIn);
                    int position = -1;
                    for (int index = 0; index < rawMap.Length - blockTagTitle.Length; index++)
                    {
                        if (rawMap[index] == blockTagTitle[0]
                            && rawMap[index + 1] == blockTagTitle[1]
                            && rawMap[index + 2] == blockTagTitle[2]
                            && rawMap[index + 3] == blockTagTitle[3]
                            && rawMap[index + 4] == blockTagTitle[4]
                            && rawMap[index + 5] == blockTagTitle[5]
                            && rawMap[index + 6] == blockTagTitle[6]
                            && rawMap[index + 7] == blockTagTitle[7]
                            && rawMap[index + 8] == blockTagTitle[8]
                            && rawMap[index + 9] == blockTagTitle[9]
                            && rawMap[index + 10] == blockTagTitle[10]
                            && rawMap[index + 11] == blockTagTitle[11]
                            )
                        {
                            position = index;
                            break;
                        }
                    }
                    if (position != -1 && position < rawMap.Length)
                    {
                        inReader.BaseStream.Seek(position + blockTagTitle.Length, SeekOrigin.Begin);

                        byte[] lengthBytes = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(lengthBytes);
                        }
                        int length = BitConverter.ToInt32(lengthBytes, 0);
                        byte[] data = inReader.ReadBytes(length);
                        return data;
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            else
            {
                return null;
            }
        }

        private byte[] loadBlockLighting()
        {
            byte[] blockTagTitle = new byte[10];
            blockTagTitle[0] = 0x42;
            blockTagTitle[1] = 0x6C;
            blockTagTitle[2] = 0x6F;
            blockTagTitle[3] = 0x63;
            blockTagTitle[4] = 0x6B;
            blockTagTitle[5] = 0x4C;
            blockTagTitle[6] = 0x69;
            blockTagTitle[7] = 0x67;
            blockTagTitle[8] = 0x68;
            blockTagTitle[9] = 0x74;
            if (this.hasBlocks)
            {
                byte[] rawMap = this._region.ReadChunk(this.LocalX, this.LocalY);
                using (MemoryStream bytesIn = new MemoryStream(rawMap))
                {
                    BinaryReader inReader = new BinaryReader(bytesIn);
                    int position = -1;
                    for (int index = 0; index < rawMap.Length - blockTagTitle.Length; index++)
                    {
                        if (rawMap[index] == blockTagTitle[0]
                            && rawMap[index + 1] == blockTagTitle[1]
                            && rawMap[index + 2] == blockTagTitle[2]
                            && rawMap[index + 3] == blockTagTitle[3]
                            && rawMap[index + 4] == blockTagTitle[4]
                            && rawMap[index + 5] == blockTagTitle[5]
                            && rawMap[index + 6] == blockTagTitle[6]
                            && rawMap[index + 7] == blockTagTitle[7]
                            && rawMap[index + 8] == blockTagTitle[8]
                            && rawMap[index + 9] == blockTagTitle[9]
                            )
                        {
                            position = index;
                            break;
                        }
                    }

                    if (position != -1 && position < rawMap.Length)
                    {
                        inReader.BaseStream.Seek(position + blockTagTitle.Length, SeekOrigin.Begin);

                        byte[] lengthBytes = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(lengthBytes);
                        }
                        int length = BitConverter.ToInt32(lengthBytes, 0);
                        byte[] data = inReader.ReadBytes(length);
                        // Lighting data is 4 bits per chunk. To make it easier to parse, we're converting it to 1 byte ber chunk.
                        byte[] lightingData = new byte[data.Length * 2];
                        for (int dataIndex = 0; dataIndex < data.Length; dataIndex++)
                        {
                            lightingData[dataIndex * 2] = (byte)((data[dataIndex] & 0x0F >> 0));
                            lightingData[dataIndex * 2 + 1] = (byte)((data[dataIndex] & 0xF0 >> 4));
                        }
                        return lightingData;
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            else
            {
                return null;
            }
        }

        private byte[] loadSkyLighting()
        {
            byte[] blockTagTitle = new byte[8];
            blockTagTitle[0] = 0x53;
            blockTagTitle[1] = 0x6B;
            blockTagTitle[2] = 0x79;
            blockTagTitle[3] = 0x4C;
            blockTagTitle[4] = 0x69;
            blockTagTitle[5] = 0x67;
            blockTagTitle[6] = 0x68;
            blockTagTitle[7] = 0x74;
            if (this.hasBlocks)
            {
                byte[] rawMap = this._region.ReadChunk(this.LocalX, this.LocalY);
                using (MemoryStream bytesIn = new MemoryStream(rawMap))
                {
                    BinaryReader inReader = new BinaryReader(bytesIn);
                    int position = -1;
                    for (int index = 0; index < rawMap.Length - blockTagTitle.Length; index++)
                    {
                        if (rawMap[index] == blockTagTitle[0]
                            && rawMap[index + 1] == blockTagTitle[1]
                            && rawMap[index + 2] == blockTagTitle[2]
                            && rawMap[index + 3] == blockTagTitle[3]
                            && rawMap[index + 4] == blockTagTitle[4]
                            && rawMap[index + 5] == blockTagTitle[5]
                            && rawMap[index + 6] == blockTagTitle[6]
                            && rawMap[index + 7] == blockTagTitle[7]
                            )
                        {
                            position = index;
                            break;
                        }
                    }

                    if (position != -1 && position < rawMap.Length)
                    {
                        inReader.BaseStream.Seek(position + blockTagTitle.Length, SeekOrigin.Begin);

                        byte[] lengthBytes = inReader.ReadBytes(4);
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(lengthBytes);
                        }
                        int length = BitConverter.ToInt32(lengthBytes, 0);
                        byte[] data = inReader.ReadBytes(length);
                        // Lighting data is 4 bits per chunk. To make it easier to parse, we're converting it to 1 byte ber chunk.
                        byte[] lightingData = new byte[data.Length * 2];
                        for (int dataIndex = 0; dataIndex < data.Length; dataIndex++)
                        {
                            lightingData[dataIndex * 2] = (byte)((data[dataIndex] & 0x0F >> 0));
                            lightingData[dataIndex * 2 + 1] = (byte)((data[dataIndex] & 0xF0 >> 4));
                        }
                        return lightingData;
                    }
                    else
                    {
                        return null;
                    }
                }
            }
            else
            {
                return null;
            }
        }

        private byte[] loadLevelChunk(FileInfo fileInfo)
        {
            using (FileStream inFile = fileInfo.OpenRead())
            {
                using (MemoryStream bytesOut = new MemoryStream())
                {
                    using (GZipStream decompressor = new GZipStream(inFile, CompressionMode.Decompress))
                    {
                        try
                        {
                            decompressor.CopyTo(bytesOut);
                        }
                        catch (System.IO.InvalidDataException GzipException)
                        {
                            #if DEBUG
                            Console.WriteLine("Chunk with bad header: " + fileInfo.Name);
                            #endif
                        }
                    }
                    return bytesOut.ToArray();
                }
            }
        }

        public static long DecodeBase36(string Base36String)
        {
            string chars = "0123456789abcdefghijklmnopqrstuvwxyz";

            bool Negative = Base36String.StartsWith("-");
            if (Negative)
                Base36String = Base36String.TrimStart(new char[] { '-' });

            Stack<char> InputStack = new Stack<char>();
            foreach (char c in Base36String.ToLower())
            {
                InputStack.Push(c);
            }
            StringBuilder InputString = new StringBuilder();
            while (InputStack.Count > 0)
            {
                InputString.Append(InputStack.Pop());
            }

            long Output = 0; int Position = 0;
            foreach (char c in InputString.ToString())
            {
                Output += chars.IndexOf(c) * (long)Math.Pow(36, Position);
                Position++;
            }

            return (Negative ? Output * -1 : Output);

        }

        private Color blendColors(Color A, Color B, int h)
        {
            Color C = new Color();
            float Aa = A.ScA;
            float Ba = B.ScA;
            float Alpha = Aa + Ba * (1 - Aa);
            C.R = (byte)((float)A.R * Aa + (float)B.R * Ba * (1 - Aa) * (float)h/128 / Alpha);
            C.G = (byte)((float)A.G * Aa + (float)B.G * Ba * (1 - Aa) * (float)h / 128 / Alpha);
            C.B = (byte)((float)A.B * Aa + (float)B.B * Ba * (1 - Aa) * (float)h / 128 / Alpha);
            C.A = (byte)(Alpha * 255);
            return C;
        }

        public int CompareTo(LevelChunk other)
        {
            int xComp = WorldX.CompareTo(other.WorldX);
            if (xComp != 0)
            {
                return xComp;
            }
            else
            {
                return -WorldY.CompareTo(other.WorldY);
            }
        }

        public string uniqueCode()
        {
            return "C" + this.WorldX + "x" + this.WorldY;
        }

        public bool Equals(LevelChunk other)
        {
            return this.uniqueCode().Equals(other.uniqueCode());
        }

        public bool isDependant(LevelChunk other)
        {
            int xOverlap = 0;
            int yOverlap = 0;
            if (settings.TileType.Equals(ImageType.OBLIQUE))
            {
                yOverlap = 8;
            }
            if ((this.WorldX <= other.WorldX + xOverlap
                                && this.WorldX >= other.WorldX)
                                &&
                                (this.WorldY <= other.WorldY + yOverlap
                                && this.WorldY >= other.WorldY))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}
